/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#ifndef NODECPP_DATA_REL_SQLPREPAREDSTATEMENT_H_
#define NODECPP_DATA_REL_SQLPREPAREDSTATEMENT_H_

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif

#include <cassert>
#include <cstdint>
#include <exception>
#include <string>
#include <vector>
#include <sstream>
#include <memory>
#include <NodeCpp/Macros.h>
#include <NodeCpp/AutoPtr.h>

namespace NodeCpp {
namespace Data {
namespace Rel {
    class SqlConnection;
    class Session;

    union SqlStmtField
    {
        bool Boolean;
        std::uint8_t Ui8;
        std::int8_t I8;
        std::uint16_t Ui16;
        std::int16_t I16;
        std::uint32_t Ui32;
        std::int32_t I32;
        std::uint64_t Ui64;
        std::int64_t I64;
        float Float;
        double Double;
    };

    enum SqlStmtFieldType
    {
        FIELD_BOOL,
        FIELD_UI8,
        FIELD_UI16,
        FIELD_UI32,
        FIELD_UI64,
        FIELD_I8,
        FIELD_I16,
        FIELD_I32,
        FIELD_I64,
        FIELD_FLOAT,
        FIELD_DOUBLE,
        FIELD_STRING,
        FIELD_NONE
    };


    class SqlStmtFieldData
    {
    public:
		SqlStmtFieldData(void) : Type_(FIELD_NONE) { BinaryData_.Ui64 = 0; }
        ~SqlStmtFieldData(void) {}

        template<typename T>
        SqlStmtFieldData(T _Param) { Set(_Param); }

        template<typename T1>
        void Set(T1 _Param1);

		bool AsBool(void) const { NODECPP_ASSERT(Type_ == FIELD_BOOL); return BinaryData_.Ui8 != 0; }
		std::uint8_t AsUint8(void) const { NODECPP_ASSERT(Type_ == FIELD_UI8); return BinaryData_.Ui8; }
		std::int8_t AsInt8(void) const{ NODECPP_ASSERT(Type_ == FIELD_I8); return BinaryData_.I8; }
		std::uint16_t AsUint16(void) const { NODECPP_ASSERT(Type_ == FIELD_UI16); return BinaryData_.Ui16; }
		std::int16_t AsInt16(void) const { NODECPP_ASSERT(Type_ == FIELD_I16); return BinaryData_.I16; }
		std::uint32_t AsUint32(void) const { NODECPP_ASSERT(Type_ == FIELD_UI32); return BinaryData_.Ui32; }
		std::int32_t AsInt32(void) const { NODECPP_ASSERT(Type_ == FIELD_I32); return BinaryData_.I32; }
		std::uint64_t AsUint64(void) const { NODECPP_ASSERT(Type_ == FIELD_UI64); return BinaryData_.Ui64; }
		std::int64_t AsInt64(void) const { NODECPP_ASSERT(Type_ == FIELD_I64); return BinaryData_.I64; }
		float AsFloat(void) const { NODECPP_ASSERT(Type_ == FIELD_FLOAT); return BinaryData_.Float; }
		double AsDouble(void) const { NODECPP_ASSERT(Type_ == FIELD_DOUBLE); return BinaryData_.Double; }
        const char* AsStr(void) const { NODECPP_ASSERT(Type_ == FIELD_STRING); return StringData_.c_str(); }

		SqlStmtFieldType type(void) const { return Type_; }
        void* buff() const { return Type_ == FIELD_STRING ? (void*)StringData_.c_str() : (void*)&BinaryData_; }
        size_t size() const
        {
			switch (Type_)
            {
            case FIELD_NONE:    return 0;
            case FIELD_BOOL:    // return sizeof(bool);
            case FIELD_UI8:     return sizeof(std::uint8_t);
            case FIELD_UI16:    return sizeof(std::uint16_t);
            case FIELD_UI32:    return sizeof(std::uint32_t);
            case FIELD_UI64:    return sizeof(std::uint64_t);
            case FIELD_I8:      return sizeof(std::int8_t);
            case FIELD_I16:     return sizeof(std::int16_t);
            case FIELD_I32:     return sizeof(std::int32_t);
            case FIELD_I64:     return sizeof(std::int64_t);
            case FIELD_FLOAT:   return sizeof(float);
            case FIELD_DOUBLE:  return sizeof(double);
            case FIELD_STRING:  return StringData_.length();

            default:
                throw std::runtime_error("unrecognized type of SqlStmtFieldType obtained");
            }
        }

    private:
        SqlStmtFieldType Type_;
        SqlStmtField BinaryData_;
        std::string StringData_;
    };

	template<> inline void SqlStmtFieldData::Set(bool _Val) { Type_ = FIELD_BOOL; BinaryData_.Ui8 = _Val; }
	template<> inline void SqlStmtFieldData::Set(std::uint8_t _Val) { Type_ = FIELD_UI8; BinaryData_.Ui8 = _Val; }
	template<> inline void SqlStmtFieldData::Set(std::int8_t _Val) { Type_ = FIELD_I8; BinaryData_.I8 = _Val; }
	template<> inline void SqlStmtFieldData::Set(std::uint16_t _Val) { Type_ = FIELD_UI16; BinaryData_.Ui16 = _Val; }
	template<> inline void SqlStmtFieldData::Set(std::int16_t _Val) { Type_ = FIELD_I16; BinaryData_.I16 = _Val; }
	template<> inline void SqlStmtFieldData::Set(std::uint32_t _Val) { Type_ = FIELD_UI32; BinaryData_.Ui32 = _Val; }
	template<> inline void SqlStmtFieldData::Set(std::int32_t _Val) { Type_ = FIELD_I32; BinaryData_.I32 = _Val; }
	template<> inline void SqlStmtFieldData::Set(std::uint64_t _Val) { Type_ = FIELD_UI64; BinaryData_.Ui64 = _Val; }
	template<> inline void SqlStmtFieldData::Set(std::int64_t _Val) { Type_ = FIELD_I64; BinaryData_.I64 = _Val; }
	template<> inline void SqlStmtFieldData::Set(float _Val) { Type_ = FIELD_FLOAT; BinaryData_.Float = _Val; }
	template<> inline void SqlStmtFieldData::Set(double _Val) { Type_ = FIELD_DOUBLE; BinaryData_.Double = _Val; }
    template<> inline void SqlStmtFieldData::Set(const char* _Val) { Type_ = FIELD_STRING; StringData_ = _Val; }


    class SqlStatement;

    class SqlStmtParameters
    {
    public:
        typedef std::vector<SqlStmtFieldData> ParameterContainer;
        explicit SqlStmtParameters(std::uint32_t _ParamsNum);

        ~SqlStmtParameters(void) {}
        ParameterContainer::size_type BoundParams(void) const { return Params_.size(); }
        void AddParam(const SqlStmtFieldData& _Data) { Params_.push_back(_Data); }
        void reset(const SqlStatement& _Stmt);
        void swap(SqlStmtParameters& _Obj);
        const ParameterContainer& params(void) const { return Params_; }

    private:
        SqlStmtParameters& operator=(const SqlStmtParameters&);
        ParameterContainer Params_;
    };

    class SqlStatementID
    {
    public:
        SqlStatementID(void) : Initialized_(false) {}
        int id(void) const { return Index_; }
        std::uint32_t GetArgumentsCount(void) const { return Arguments_; }
        bool IsInitialized(void) const { return Initialized_; }

    private:
        friend class Session;
        void _Init(int _ID, std::uint32_t _Args) { Index_ = _ID; Arguments_ = _Args; Initialized_ = true; }

        int Index_;
        std::uint32_t Arguments_;
        bool Initialized_;
    };


    class SqlStatement
    {
    public:
        ~SqlStatement(void) {  }

        SqlStatement(const SqlStatement& _Statement)
            : Index_(_Statement.Index_), Session_(_Statement.Session_), Params_(NULL)
        {
            if (_Statement.Params_.get()) {
                Params_.reset(new SqlStmtParameters(*(_Statement.Params_)));
            }
        }

        SqlStatement& operator=(const SqlStatement& _Index);
        int id(void) const { return Index_.id(); }
        std::uint32_t GetArgumentsCount() const { return Index_.GetArgumentsCount(); }
        bool Execute(void);
        bool DirectExecute(void);

        template<typename ParamType1>
        bool PExecute(ParamType1 _Param1)
        {
            _Arg(_Param1);
            return Execute();
        }

        template<typename ParamType1, typename ParamType2>
        bool PExecute(ParamType1 _Param1, ParamType2 _Param2)
        {
            _Arg(_Param1);
            _Arg(_Param2);
            return Execute();
        }

        template<typename ParamType1, typename ParamType2, typename ParamType3>
        bool PExecute(ParamType1 _Param1, ParamType2 _Param2, ParamType3 _Param3)
        {
            _Arg(_Param1);
            _Arg(_Param2);
            _Arg(_Param3);
            return Execute();
        }

        template<typename ParamType1, typename ParamType2, typename ParamType3, typename ParamType4>
        bool PExecute(ParamType1 _Param1, ParamType2 _Param2, ParamType3 _Param3, ParamType4 _Param4)
        {
            _Arg(_Param1);
            _Arg(_Param2);
            _Arg(_Param3);
            _Arg(_Param4);
            return Execute();
        }

        void AddBool(bool _Var) { _Arg(_Var); }
        void AddUInt8(::std::uint8_t _Var) { _Arg(_Var); }
        void AddInt8(::std::int8_t _Var) { _Arg(_Var); }
        void AddUInt16(::std::uint16_t _Var) { _Arg(_Var); }
        void AddInt16(::std::int16_t _Var) { _Arg(_Var); }
        void AddUInt32(::std::uint32_t _Var) { _Arg(_Var); }
        void AddInt32(::std::int32_t _Var) { _Arg(_Var); }
        void AddUInt64(::std::uint64_t _Var) { _Arg(_Var); }
        void AddInt64(::std::int64_t _Var) { _Arg(_Var); }
        void AddFloat(float _Var) { _Arg(_Var); }
        void AddDouble(double _Var) { _Arg(_Var); }
        void AddString(const char* _Var) { _Arg(_Var); }
        void AddString(const std::string& _Var) { _Arg(_Var.c_str()); }
        void AddString(std::ostringstream& _Ss) { _Arg(_Ss.str().c_str()); _Ss.str(std::string()); }

    protected:
        friend class Session;
        SqlStatement(const SqlStatementID& _Index, Session& _Sess)
            : Index_(_Index), Session_(&_Sess), Params_(NULL) {}

    private:
        SqlStmtParameters* _Get()
        {
            if (!Params_.get()) {
                Params_.reset(new SqlStmtParameters(GetArgumentsCount()));
            }
            return Params_.get();
        }

        SqlStmtParameters* _Detach()
        {
            SqlStmtParameters* _P = Params_.get() ? Params_.release() : new SqlStmtParameters(0);
            return _P;
        }

        template<typename ParamType>
        void _Arg(ParamType _Val)
        {
            SqlStmtParameters* _P = _Get();
            _P->AddParam(SqlStmtFieldData(_Val));
        }


        SqlStatementID Index_;
        Session* Session_;
        AutoPtr<SqlStmtParameters> Params_;
    };

    class SqlPreparedStatement
    {
    public:
        virtual ~SqlPreparedStatement(void) {}
        bool IsPrepared(void) const { return Prepared_; }
        bool IsQuery(void) const { return Query_; }
        std::uint32_t GetParamsCount(void) const { return Params_; }
        std::uint32_t GetColumnsCount(void) const { return IsQuery() ? Columns_ : 0; }
        virtual bool Prepare(void) = 0;
        virtual void Bind(const SqlStmtParameters& holder) = 0;
        virtual bool Execute(void) = 0;

    protected:
        SqlPreparedStatement(const std::string& _Fmt, SqlConnection& _Conn) :
            Params_(0), Columns_(0), Query_(false),
            Prepared_(false), Format_(_Fmt), Conn_(_Conn)
        {}

        std::uint32_t Params_;
        std::uint32_t Columns_;
        bool Query_;
        bool Prepared_;
        std::string Format_;
        SqlConnection& Conn_;
    };

    class SqlPlainPreparedStatement : public SqlPreparedStatement
    {
    public:
        SqlPlainPreparedStatement(const std::string& _Fmt, SqlConnection& _Conn);
        ~SqlPlainPreparedStatement(void) {}
        virtual bool Prepare(void) override { return true; }
        virtual void Bind(const SqlStmtParameters& _Holder) override;
        virtual bool Execute(void) override;

    protected:
        void dataToString(const SqlStmtFieldData& _Data, std::ostringstream& _Fmt);

        std::string PlainRequest_;
    };

}}}

#endif // NODECPP_DATA_REL_SQLPREPAREDSTATEMENT_H_
